using System;
using System.Drawing;
using System.Windows.Forms;
using Alegris.Enumerations;

namespace Alegris
{
    public partial class AlegrisGame : Form
    {
        private ToolStripMenuItem _currentMenuSkin;

        private Boolean _useOctahedronStyle;

        private Piece _nextPiece;
        private Piece _currentPiece;

        private GameStatus _gameStatus;
        private PieceShape[,] _board;
        private readonly Timer _timer;

        private int _totalScore;
        private int _currentLevel;
        private int _totalNumberLines;

        public AlegrisGame()
        {
            InitializeComponent();

            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.DoubleBuffer |
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint,
                true);

            _currentMenuSkin = theme1ToolStripMenuItem;

            InitializeField();

            _useOctahedronStyle = false;
            _gameStatus = GameStatus.Stoped;
            _timer = new Timer();
            _timer.Tick += TimerTick;
            KeyDown += AlegrisGameKeyDown;
        }

        #region GameEngine

        private void StarGame()
        {
            _totalScore = 0;
            _totalNumberLines = 0;
            _currentPiece = null;
            _nextPiece = null;
            _gameStatus = GameStatus.Running;
            imgPaused.Visible = false;

            InitializeField();
            UpdateLevel(0);
            _timer.Start();

            Invalidate();
        }

        private void SwitchPause()
        {
            if (_gameStatus != GameStatus.Paused)
            {
                Pause();
            }
            else
            {
                Unpause();
            }
        }

        private void Pause()
        {
            _timer.Enabled = false;
            _gameStatus = GameStatus.Paused;
            imgPaused.Visible = true;

            Invalidate();
        }

        private void Unpause()
        {
            _timer.Enabled = true;
            _gameStatus = GameStatus.Running;
            imgPaused.Visible = false;

            Invalidate();
        }

        private Piece CreatePiece()
        {
            if (_nextPiece == null)
            {
                _nextPiece = PieceFactory.CreateRandomPiece();
            }

            _currentPiece = _nextPiece;
            _nextPiece = PieceFactory.CreateRandomPiece();

            Invalidate(new Rectangle(216, 35, 116, 100));

            return _currentPiece;
        }

        private void InitializeField()
        {
            _board = new PieceShape[Setup.Cols, Setup.Rows];
            for (var i = 0; i < Setup.Cols; i++)
            {
                for (var j = 0; j < Setup.Rows; j++)
                {
                    _board[i, j] = PieceShape.None;
                }
            }
        }

        private void MovePiece(Side side)
        {
            if (_currentPiece == null)
            {
                _currentPiece = CreatePiece();
            }

            if (CollisionDetection(_currentPiece, side) == false)
            {
                switch (side)
                {
                    case Side.Left: _currentPiece.Position.X--; break;
                    case Side.Right: _currentPiece.Position.X++; break;
                    case Side.Down: _currentPiece.Position.Y++; break;
                }
            }
            else if (CollisionDetection(_currentPiece, Side.Down))
            {
                MovePieceToPile();
                ClearLines();
                CheckGameOver();
                CreatePiece();
            }

            var rect = new Rectangle(
                (_currentPiece.Position.X * Setup.BlockSize) - Setup.BlockSize + Setup.BoardMarginLeft,
                (_currentPiece.Position.Y * Setup.BlockSize) - Setup.BlockSize + Setup.BoardMarginTop,
                _currentPiece.Dimention.X * Setup.BlockSize + (3 * Setup.BlockSize),
                _currentPiece.Dimention.Y * Setup.BlockSize + Setup.BlockSize + 1);

            Invalidate(rect);
        }

        private void RotatePiece()
        {
            var newPiece = (Piece)_currentPiece.Clone();
            newPiece.Rotate();

            if (newPiece.Position.X + newPiece.Dimention.X > (Setup.Cols)) return;
            if (newPiece.Position.Y + newPiece.Dimention.Y > (Setup.Rows)) return;
            if (CollisionDetection(newPiece, Side.Up)) return;

            var oldPiece = _currentPiece;
            _currentPiece = newPiece;

            Rectangle rect;
            if (oldPiece.Dimention.X > _currentPiece.Dimention.X)
            {
                rect = new Rectangle(
                    _currentPiece.Position.X * Setup.BlockSize - Setup.BlockSize - 20,
                    _currentPiece.Position.Y * Setup.BlockSize - Setup.BlockSize,
                    oldPiece.Dimention.X * Setup.BlockSize + oldPiece.Dimention.X * Setup.BlockSize,
                    _currentPiece.Dimention.Y * Setup.BlockSize + _currentPiece.Dimention.Y * Setup.BlockSize);
            }
            else
            {
                rect = new Rectangle(
                    _currentPiece.Position.X * Setup.BlockSize - (Setup.BlockSize * 2),
                    _currentPiece.Position.Y * Setup.BlockSize - Setup.BlockSize,
                    _currentPiece.Dimention.X * Setup.BlockSize + _currentPiece.Dimention.X * Setup.BlockSize,
                    (oldPiece.Dimention.Y * Setup.BlockSize) + (oldPiece.Dimention.Y * Setup.BlockSize));
            }

            Invalidate(rect);
        }

        private void MovePieceToPile()
        {
            for (var i = 0; i < _currentPiece.Dimention.X; i++)
            {
                for (var j = 0; j < _currentPiece.Dimention.Y; j++)
                {
                    if (_currentPiece.Shape[i, j] == PieceShape.None) continue;
                    
                    var boardX = _currentPiece.Position.X + i;
                    var boardY = _currentPiece.Position.Y + j;
                    _board[boardX, boardY] = _currentPiece.Shape[i, j];
                }
            }
        }

        private void UpdateLevel(int level)
        {
            level = level < 1 ? 1 : level;
            _currentLevel = level;
            _timer.Interval = (1000 / level);

            labelLevelShow.Text = Convert.ToString(_currentLevel);
            labelLinesShow.Text = Convert.ToString(_totalNumberLines);
            labelScoreShow.Text = Convert.ToString(_totalScore);
        }

        private bool CollisionDetection(Piece piece, Side side)
        {
            if ((side == Side.Left) && (piece.Position.X < 1)) return true;
            if ((side == Side.Right) && (piece.Position.X + piece.Dimention.X > (Setup.Cols - 1))) return true;
            if ((side == Side.Down) && (piece.Position.Y + piece.Dimention.Y > (Setup.Rows - 1))) return true;

            for (var i = 0; i < piece.Dimention.X; i++)
            {
                for (var j = 0; j < piece.Dimention.Y; j++)
                {
                    var boardX = piece.Position.X + i;
                    var boardY = piece.Position.Y + j;

                    switch (side)
                    {
                        case Side.Left: boardX--; break;
                        case Side.Right: boardX++; break;
                        case Side.Down: boardY++; break;
                    }

                    if (boardX < 0)
                    {
                        return true;
                    }

                    if (piece.Shape[i, j] != PieceShape.None && _board[boardX, boardY] != PieceShape.None)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private void ClearLines()
        {
            var numberLines = 0;

            for (int j = (Setup.Rows - 1); j >= 0; j--)
            {
                bool linefull = true;

                for (int i = 0; i < Setup.Cols; i++)
                {
                    if (_board[i, j] == PieceShape.None)
                    {
                        linefull = false;
                        break;
                    }
                }
                if (linefull)
                {
                    ClearLine(j);
                    numberLines++;
                    j++;
                }
            }

            if (numberLines > 0)
            {
                int score = numberLines == 1 ? 40 :
                            numberLines == 2 ? 100 :
                            numberLines == 3 ? 300 : 1200;

                _totalScore += _currentLevel * score;
            }

            _currentLevel =
                _totalNumberLines < 10 ? 1 :
                _totalNumberLines < 20 ? 2 :
                _totalNumberLines < 30 ? 3 :
                _totalNumberLines < 40 ? 4 :
                _totalNumberLines < 50 ? 5 :
                _totalNumberLines < 60 ? 6 :
                _totalNumberLines < 70 ? 7 :
                _totalNumberLines < 80 ? 8 :
                _totalNumberLines < 90 ? 9 : 10;

            UpdateLevel(_currentLevel);

            Invalidate();
        }

        private void ClearLine(int index)
        {
            for (int j = index; j > 0; j--)
            {
                for (int i = 0; i < Setup.Cols; i++)
                {
                    _board[i, j] = _board[i, j - 1];
                }
            }

            _totalNumberLines++;
        }

        private void CheckGameOver()
        {
            for (int i = 0; i < Setup.Cols; i++)
            {
                if (_board[i, 0] == PieceShape.None) continue;
                
                _timer.Stop();
                _gameStatus = GameStatus.GameOver;
            }
        }

        #endregion

        #region Graphic Engine

        private void DrawNextPiece(Graphics gfx)
        {
            for (int i = 0; i < _nextPiece.Dimention.X; i++)
            {
                for (int j = 0; j < _nextPiece.Dimention.Y; j++)
                {
                    if (_nextPiece.Shape[i, j] == PieceShape.None) continue;

                    var rect = new Rectangle(
                        Setup.NextPiecePanelLeft + (Setup.NextPieceWidth / 2) - (_nextPiece.Dimention.X / 2) + (i * Setup.BlockSize) - Setup.BlockSize,
                        Setup.NextPiecePanelTop + (Setup.NextPiecePanelHeight / 2) - (_nextPiece.Dimention.Y / 2) + (j * Setup.BlockSize) - 2 * Setup.BlockSize, Setup.BlockSize, Setup.BlockSize);
                    if (_useOctahedronStyle)
                    {
                        gfx.DrawImage(Setup.GetPieceBitmap(_nextPiece.Shape[i, j]), rect);
                    }
                    else
                    {
                        gfx.FillRectangle(new SolidBrush(Setup.GetPieceColor(_nextPiece.Shape[i, j])), rect);
                        gfx.DrawRectangle(new Pen(Color.Black), rect);
                    }
                }
            }
        }

        private void DrawCurrentPiece(Graphics gfx)
        {
            for (int i = 0; i < _currentPiece.Dimention.X; i++)
            {
                for (int j = 0; j < _currentPiece.Dimention.Y; j++)
                {
                    if (_currentPiece.Shape[i, j] == PieceShape.None)
                    {
                        continue;
                    }

                    var rect = new Rectangle(
                        (_currentPiece.Position.X + i) * Setup.BlockSize + Setup.BoardMarginLeft,
                        (_currentPiece.Position.Y + j) * Setup.BlockSize + Setup.BoardMarginTop,
                         Setup.BlockSize, Setup.BlockSize);

                    if (_useOctahedronStyle)
                    {
                        gfx.DrawImage(Setup.GetPieceBitmap(_currentPiece.Shape[i, j]), rect);
                    }
                    else
                    {
                        gfx.FillRectangle(new SolidBrush(Setup.GetPieceColor(_currentPiece.Shape[i, j])), rect);
                        gfx.DrawRectangle(new Pen(Color.Black), rect);
                    }
                }
            }
        }

        private void DrawPile(Graphics gfx)
        {
            for (var i = 0; i < Setup.Cols; i++)
            {
                for (var j = 0; j < Setup.Rows; j++)
                {
                    if (_board[i, j] == PieceShape.None) continue;
                    
                    var rect = new Rectangle(
                        (i * Setup.BlockSize + Setup.BoardMarginLeft),
                        (j * Setup.BlockSize + Setup.BoardMarginTop), Setup.BlockSize, Setup.BlockSize);

                    if (_useOctahedronStyle)
                    {
                        gfx.DrawImage(Setup.GetPieceBitmap(_board[i, j]), rect);
                    }
                    else
                    {
                        gfx.FillRectangle(new SolidBrush(Setup.GetPieceColor(_board[i, j])), rect);
                        gfx.DrawRectangle(new Pen(Color.Black), rect);
                    }
                }
            }
        }

        private void HideEveryThing(Graphics gfx)
        {
            var rectangle = new Rectangle(Setup.BoardMarginLeft, Setup.BoardMarginTop, Setup.BoardWidth, Setup.BoardHeight);
            gfx.FillRectangle(new SolidBrush(Color.Black), rectangle);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            switch (_gameStatus)
            {
                case GameStatus.Paused:
                {
                    HideEveryThing(e.Graphics);
                    break;
                }
                case GameStatus.GameOver:
                {
                    break;
                }
                case GameStatus.Running:
                {
                    if (_currentPiece != null)
                    {
                        DrawNextPiece(e.Graphics);
                        DrawCurrentPiece(e.Graphics);
                    }

                    DrawPile(e.Graphics);

                    break;
                }
            }

            base.OnPaint(e);
        }

        #endregion

        #region Events

        private void AlegrisGameKeyDown(object sender, KeyEventArgs e)
        {
            if (_gameStatus == GameStatus.Running)
            {
                if (e.KeyData == Joystick.Left) MovePiece(Side.Left);
                else if (e.KeyData == Joystick.Right) MovePiece(Side.Right);
                else if (e.KeyData == Joystick.Down) MovePiece(Side.Down);
                else if (e.KeyData == Joystick.Rotate) RotatePiece();
            }
        }

        private void TimerTick(object sender, EventArgs e)
        {
            MovePiece(Side.Down);
            Focus();
        }

        private void NewGameToolStripMenuItemClick(object sender, EventArgs e)
        {
            StarGame();
        }

        private void PauseToolStripMenuItemClick(object sender, EventArgs e)
        {
            SwitchPause();
        }

        private void QuitToolStripMenuItemClick(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void SkinToolStripMenuItemClick(object sender, EventArgs e)
        {
            _currentMenuSkin.Checked = false;
            _currentMenuSkin = (ToolStripMenuItem)sender;
            _currentMenuSkin.Checked = true;

            switch (_currentMenuSkin.Tag.ToString())
            {
                case "1": BackgroundImage = Properties.Resources.baseSkin1; break;
                case "2": BackgroundImage = Properties.Resources.baseSkin2; break;
                case "3": BackgroundImage = Properties.Resources.baseSkin3; break;
                case "4": BackgroundImage = Properties.Resources.baseSkin4; break;
                case "5": BackgroundImage = Properties.Resources.baseSkin5; break;
                case "6": BackgroundImage = Properties.Resources.baseSkin6; break;
            }
        }

        private void UseOctahedronToolStripMenuItemClick(object sender, EventArgs e)
        {
            _useOctahedronStyle = !_useOctahedronStyle;
            Invalidate();
        }

        private void SetupJoystickToolStripMenuItemClick(object sender, EventArgs e)
        {
            var config = new ControlsConfig();
            config.ShowDialog();
        }

        private void AboutToolStripMenuItemClick(object sender, EventArgs e)
        {
            var about = new AboutAlegris();
            about.ShowDialog();
        }

        #endregion
    }
}